package com.template.web.utils;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Slf4j
public class JsonUtil {

    private static ObjectMapper getObjectMapper() {
        return JacksonHolder.INSTANCE;
    }

    private static class JacksonHolder {
        private static final ObjectMapper INSTANCE;
        static {
            JsonFactory jsonFactory = JsonFactory.builder()
                    .configure(JsonReadFeature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true)
                    .configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS, true)
                    .build();
            SimpleModule simpleModule = new SimpleModule();
            simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
            javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
            javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
            javaTimeModule.addSerializer(Instant.class, InstantSerializer.INSTANCE);
            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
            javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
            javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
            javaTimeModule.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
            INSTANCE = JsonMapper.builder(jsonFactory)
                    .defaultLocale(Locale.CHINA)
                    .configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true)
                    .configure(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS, true)
                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                    .configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true)
                    .configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true)
                    .serializationInclusion(JsonInclude.Include.NON_NULL)
                    .addModules(simpleModule, javaTimeModule)
                    .build();
            INSTANCE.findAndRegisterModules();
        }
    }

    public static String toJsonString(Object object) {
        try {
            return getObjectMapper().writeValueAsString(object);
        } catch (Exception e) {
            log.error("object to json error", e);
        }
        return null;
    }

    public static <T> T parseObject(String json, Class<T> clazz) {
        if (StrUtil.isBlank(json)) {
            return null;
        }
        try {
            return getObjectMapper().readValue(json, clazz);
        } catch (Exception e) {
            log.error("parse object error", e);
        }
        return null;
    }

    public static <T> T parseObject(String json, TypeReference<T> type) {
        if (StrUtil.isBlank(json)) {
            return null;
        }
        try {
            return getObjectMapper().readValue(json, type);
        } catch (Exception e) {
            log.error("parse object error", e);
        }
        return null;
    }

    public static <T> List<T> toList(String json, Class<T> clazz) {
        if (StrUtil.isBlank(json)) {
            return Collections.emptyList();
        }
        try {
            CollectionType listType = getObjectMapper().getTypeFactory().constructCollectionType(ArrayList.class, clazz);
            return getObjectMapper().readValue(json, listType);
        } catch (Exception e) {
            log.error("parse list error", e);
        }
        return Collections.emptyList();
    }

    public static <K, V> Map<K, V> toMap(String json, Class<K> classK, Class<V> classV) {
        if (StrUtil.isBlank(json)) {
            return Collections.emptyMap();
        }
        try {
            return parseObject(json, new TypeReference<Map<K, V>>() {});
        } catch (Exception e) {
            log.error("parse map error", e);
        }
        return Collections.emptyMap();
    }
}
